// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/android/synchronous_compositor_frame_sink.h"

#include <vector>

#include "base/auto_reset.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/compositor_frame_sink_client.h"
#include "cc/output/context_provider.h"
#include "cc/output/output_surface.h"
#include "cc/output/output_surface_frame.h"
#include "cc/output/renderer_settings.h"
#include "cc/output/software_output_device.h"
#include "cc/output/texture_mailbox_deleter.h"
#include "cc/quads/render_pass.h"
#include "cc/quads/surface_draw_quad.h"
#include "cc/surfaces/display.h"
#include "cc/surfaces/surface_factory.h"
#include "cc/surfaces/surface_id_allocator.h"
#include "cc/surfaces/surface_manager.h"
#include "content/common/android/sync_compositor_messages.h"
#include "content/renderer/android/synchronous_compositor_filter.h"
#include "content/renderer/android/synchronous_compositor_registry.h"
#include "content/renderer/gpu/frame_swap_message_queue.h"
#include "content/renderer/render_thread_impl.h"
#include "gpu/command_buffer/client/context_support.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/common/gpu_memory_allocation.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/ipc_sender.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/transform.h"

namespace content {

namespace {

    const int64_t kFallbackTickTimeoutInMilliseconds = 100;
    const cc::FrameSinkId kRootFrameSinkId(1, 1);
    const cc::FrameSinkId kChildFrameSinkId(1, 2);

    // Do not limit number of resources, so use an unrealistically high value.
    const size_t kNumResourcesLimit = 10 * 1000 * 1000;

    class SoftwareDevice : public cc::SoftwareOutputDevice {
    public:
        SoftwareDevice(SkCanvas** canvas)
            : canvas_(canvas)
        {
        }

        void Resize(const gfx::Size& pixel_size, float device_scale_factor) override
        {
            // Intentional no-op: canvas size is controlled by the embedder.
        }
        SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override
        {
            DCHECK(*canvas_) << "BeginPaint with no canvas set";
            return *canvas_;
        }
        void EndPaint() override { }

    private:
        SkCanvas** canvas_;

        DISALLOW_COPY_AND_ASSIGN(SoftwareDevice);
    };

} // namespace

class SynchronousCompositorFrameSink::SoftwareOutputSurface
    : public cc::OutputSurface {
public:
    SoftwareOutputSurface(std::unique_ptr<SoftwareDevice> software_device)
        : cc::OutputSurface(std::move(software_device))
    {
    }

    // cc::OutputSurface implementation.
    void BindToClient(cc::OutputSurfaceClient* client) override { }
    void EnsureBackbuffer() override { }
    void DiscardBackbuffer() override { }
    void BindFramebuffer() override { }
    void SwapBuffers(cc::OutputSurfaceFrame frame) override { }
    void Reshape(const gfx::Size& size,
        float scale_factor,
        const gfx::ColorSpace& color_space,
        bool has_alpha,
        bool use_stencil) override { }
    uint32_t GetFramebufferCopyTextureFormat() override { return 0; }
    cc::OverlayCandidateValidator* GetOverlayCandidateValidator() const override
    {
        return nullptr;
    }
    bool IsDisplayedAsOverlayPlane() const override { return false; }
    unsigned GetOverlayTextureId() const override { return 0; }
    bool SurfaceIsSuspendForRecycle() const override { return false; }
    bool HasExternalStencilTest() const override { return false; }
    void ApplyExternalStencil() override { }
};

SynchronousCompositorFrameSink::SynchronousCompositorFrameSink(
    scoped_refptr<cc::ContextProvider> context_provider,
    scoped_refptr<cc::ContextProvider> worker_context_provider,
    gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager,
    int routing_id,
    uint32_t compositor_frame_sink_id,
    std::unique_ptr<cc::BeginFrameSource> begin_frame_source,
    SynchronousCompositorRegistry* registry,
    scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue)
    : cc::CompositorFrameSink(std::move(context_provider),
        std::move(worker_context_provider),
        gpu_memory_buffer_manager,
        nullptr)
    , routing_id_(routing_id)
    , compositor_frame_sink_id_(compositor_frame_sink_id)
    , registry_(registry)
    , sender_(RenderThreadImpl::current()->sync_compositor_message_filter())
    , memory_policy_(0u)
    , frame_swap_message_queue_(frame_swap_message_queue)
    , surface_manager_(new cc::SurfaceManager)
    , surface_id_allocator_(new cc::SurfaceIdAllocator())
    , root_factory_(new cc::SurfaceFactory(kRootFrameSinkId,
          surface_manager_.get(),
          this))
    , child_factory_(new cc::SurfaceFactory(kChildFrameSinkId,
          surface_manager_.get(),
          this))
    , begin_frame_source_(std::move(begin_frame_source))
{
    DCHECK(registry_);
    DCHECK(sender_);
    DCHECK(begin_frame_source_);
    thread_checker_.DetachFromThread();
    memory_policy_.priority_cutoff_when_visible = gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE;
}

SynchronousCompositorFrameSink::~SynchronousCompositorFrameSink() = default;

void SynchronousCompositorFrameSink::SetSyncClient(
    SynchronousCompositorFrameSinkClient* compositor)
{
    DCHECK(CalledOnValidThread());
    sync_client_ = compositor;
    if (sync_client_)
        Send(new SyncCompositorHostMsg_CompositorFrameSinkCreated(routing_id_));
}

bool SynchronousCompositorFrameSink::OnMessageReceived(
    const IPC::Message& message)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(SynchronousCompositorFrameSink, message)
    IPC_MESSAGE_HANDLER(SyncCompositorMsg_SetMemoryPolicy, SetMemoryPolicy)
    IPC_MESSAGE_HANDLER(SyncCompositorMsg_ReclaimResources, OnReclaimResources)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

bool SynchronousCompositorFrameSink::BindToClient(
    cc::CompositorFrameSinkClient* sink_client)
{
    DCHECK(CalledOnValidThread());
    if (!cc::CompositorFrameSink::BindToClient(sink_client))
        return false;

    DCHECK(begin_frame_source_);
    client_->SetBeginFrameSource(begin_frame_source_.get());
    client_->SetMemoryPolicy(memory_policy_);
    client_->SetTreeActivationCallback(
        base::Bind(&SynchronousCompositorFrameSink::DidActivatePendingTree,
            base::Unretained(this)));
    registry_->RegisterCompositorFrameSink(routing_id_, this);

    surface_manager_->RegisterFrameSinkId(kRootFrameSinkId);
    surface_manager_->RegisterFrameSinkId(kChildFrameSinkId);
    surface_manager_->RegisterSurfaceFactoryClient(kRootFrameSinkId, this);
    surface_manager_->RegisterSurfaceFactoryClient(kChildFrameSinkId, this);

    cc::RendererSettings software_renderer_settings;

    auto output_surface = base::MakeUnique<SoftwareOutputSurface>(
        base::MakeUnique<SoftwareDevice>(&current_sw_canvas_));
    software_output_surface_ = output_surface.get();

    // The shared_bitmap_manager and gpu_memory_buffer_manager here are null as
    // this Display is only used for resourcesless software draws, where no
    // resources are included in the frame swapped from the compositor. So there
    // is no need for these.
    display_.reset(new cc::Display(
        nullptr /* shared_bitmap_manager */,
        nullptr /* gpu_memory_buffer_manager */, software_renderer_settings,
        kRootFrameSinkId, nullptr /* begin_frame_source */,
        std::move(output_surface), nullptr /* scheduler */,
        nullptr /* texture_mailbox_deleter */));
    display_->Initialize(&display_client_, surface_manager_.get());
    display_->SetVisible(true);
    return true;
}

void SynchronousCompositorFrameSink::DetachFromClient()
{
    DCHECK(CalledOnValidThread());
    client_->SetBeginFrameSource(nullptr);
    // Destroy the begin frame source on the same thread it was bound on.
    begin_frame_source_ = nullptr;
    registry_->UnregisterCompositorFrameSink(routing_id_, this);
    client_->SetTreeActivationCallback(base::Closure());
    root_factory_->EvictSurface();
    child_factory_->EvictSurface();
    surface_manager_->UnregisterSurfaceFactoryClient(kRootFrameSinkId);
    surface_manager_->UnregisterSurfaceFactoryClient(kChildFrameSinkId);
    surface_manager_->InvalidateFrameSinkId(kRootFrameSinkId);
    surface_manager_->InvalidateFrameSinkId(kChildFrameSinkId);
    software_output_surface_ = nullptr;
    display_ = nullptr;
    child_factory_ = nullptr;
    root_factory_ = nullptr;
    surface_id_allocator_ = nullptr;
    surface_manager_ = nullptr;
    cc::CompositorFrameSink::DetachFromClient();
    CancelFallbackTick();
}

static void NoOpDrawCallback() { }

void SynchronousCompositorFrameSink::SubmitCompositorFrame(
    cc::CompositorFrame frame)
{
    DCHECK(CalledOnValidThread());
    DCHECK(sync_client_);

    if (fallback_tick_running_) {
        DCHECK(frame.resource_list.empty());
        cc::ReturnedResourceArray return_resources;
        ReturnResources(return_resources);
        did_submit_frame_ = true;
        return;
    }

    cc::CompositorFrame submit_frame;

    if (in_software_draw_) {
        // The frame we send to the client is actually just the metadata. Preserve
        // the |frame| for the software path below.
        submit_frame.metadata = frame.metadata.Clone();

        if (!root_local_frame_id_.is_valid()) {
            root_local_frame_id_ = surface_id_allocator_->GenerateId();
            child_local_frame_id_ = surface_id_allocator_->GenerateId();
        }

        display_->SetLocalFrameId(root_local_frame_id_,
            frame.metadata.device_scale_factor);

        // The layer compositor should be giving a frame that covers the
        // |sw_viewport_for_current_draw_| but at 0,0.
        gfx::Size child_size = sw_viewport_for_current_draw_.size();
        DCHECK(gfx::Rect(child_size) == frame.render_pass_list.back()->output_rect);

        // Make a size that covers from 0,0 and includes the area coming from the
        // layer compositor.
        gfx::Size display_size(sw_viewport_for_current_draw_.right(),
            sw_viewport_for_current_draw_.bottom());
        display_->Resize(display_size);

        // The offset for the child frame relative to the origin of the canvas being
        // drawn into.
        gfx::Transform child_transform;
        child_transform.Translate(
            gfx::Vector2dF(sw_viewport_for_current_draw_.OffsetFromOrigin()));

        // Make a root frame that embeds the frame coming from the layer compositor
        // and positions it based on the provided viewport.
        // TODO(danakj): We could apply the transform here instead of passing it to
        // the CompositorFrameSink client too? (We'd have to do the same for
        // hardware frames in SurfacesInstance?)
        cc::CompositorFrame embed_frame;
        embed_frame.render_pass_list.push_back(cc::RenderPass::Create());

        // The embedding RenderPass covers the entire Display's area.
        const auto& embed_render_pass = embed_frame.render_pass_list.back();
        embed_render_pass->SetNew(1, gfx::Rect(display_size),
            gfx::Rect(display_size), gfx::Transform());
        embed_render_pass->has_transparent_background = false;

        // The RenderPass has a single SurfaceDrawQuad (and SharedQuadState for it).
        auto* shared_quad_state = embed_render_pass->CreateAndAppendSharedQuadState();
        auto* surface_quad = embed_render_pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>();
        shared_quad_state->SetAll(
            child_transform, child_size, gfx::Rect(child_size),
            gfx::Rect() /* clip_rect */, false /* is_clipped */, 1.f /* opacity */,
            SkBlendMode::kSrcOver, 0 /* sorting_context_id */);
        surface_quad->SetNew(
            shared_quad_state, gfx::Rect(child_size), gfx::Rect(child_size),
            cc::SurfaceId(kChildFrameSinkId, child_local_frame_id_));

        child_factory_->SubmitCompositorFrame(
            child_local_frame_id_, std::move(frame), base::Bind(&NoOpDrawCallback));
        root_factory_->SubmitCompositorFrame(root_local_frame_id_,
            std::move(embed_frame),
            base::Bind(&NoOpDrawCallback));
        display_->DrawAndSwap();
    } else {
        // For hardware draws we send the whole frame to the client so it can draw
        // the content in it.
        submit_frame = std::move(frame);
    }

    sync_client_->SubmitCompositorFrame(compositor_frame_sink_id_,
        std::move(submit_frame));
    DeliverMessages();
    did_submit_frame_ = true;
}

void SynchronousCompositorFrameSink::CancelFallbackTick()
{
    fallback_tick_.Cancel();
    fallback_tick_pending_ = false;
}

void SynchronousCompositorFrameSink::FallbackTickFired()
{
    DCHECK(CalledOnValidThread());
    TRACE_EVENT0("renderer", "SynchronousCompositorFrameSink::FallbackTickFired");
    base::AutoReset<bool> in_fallback_tick(&fallback_tick_running_, true);
    SkBitmap bitmap;
    bitmap.allocN32Pixels(1, 1);
    bitmap.eraseColor(0);
    SkCanvas canvas(bitmap);
    fallback_tick_pending_ = false;
    DemandDrawSw(&canvas);
}

void SynchronousCompositorFrameSink::Invalidate()
{
    DCHECK(CalledOnValidThread());
    if (sync_client_)
        sync_client_->Invalidate();

    if (!fallback_tick_pending_) {
        fallback_tick_.Reset(
            base::Bind(&SynchronousCompositorFrameSink::FallbackTickFired,
                base::Unretained(this)));
        base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
            FROM_HERE, fallback_tick_.callback(),
            base::TimeDelta::FromMilliseconds(kFallbackTickTimeoutInMilliseconds));
        fallback_tick_pending_ = true;
    }
}

void SynchronousCompositorFrameSink::DemandDrawHw(
    const gfx::Size& viewport_size,
    const gfx::Rect& viewport_rect_for_tile_priority,
    const gfx::Transform& transform_for_tile_priority)
{
    DCHECK(CalledOnValidThread());
    DCHECK(HasClient());
    DCHECK(context_provider_.get());
    CancelFallbackTick();

    client_->SetExternalTilePriorityConstraints(viewport_rect_for_tile_priority,
        transform_for_tile_priority);
    InvokeComposite(gfx::Transform(), gfx::Rect(viewport_size));
}

void SynchronousCompositorFrameSink::DemandDrawSw(SkCanvas* canvas)
{
    DCHECK(CalledOnValidThread());
    DCHECK(canvas);
    DCHECK(!current_sw_canvas_);
    CancelFallbackTick();

    base::AutoReset<SkCanvas*> canvas_resetter(&current_sw_canvas_, canvas);

    SkIRect canvas_clip;
    canvas->getClipDeviceBounds(&canvas_clip);
    gfx::Rect viewport = gfx::SkIRectToRect(canvas_clip);

    gfx::Transform transform(gfx::Transform::kSkipInitialization);
    transform.matrix() = canvas->getTotalMatrix(); // Converts 3x3 matrix to 4x4.

    // We will resize the Display to ensure it covers the entire |viewport|, so
    // save it for later.
    sw_viewport_for_current_draw_ = viewport;

    base::AutoReset<bool> set_in_software_draw(&in_software_draw_, true);
    InvokeComposite(transform, viewport);
}

#if ENABLE_TENCENT(REPORT_SCREEN_STATUS)
void SynchronousCompositorFrameSink::NotifyScreenStatus(int status)
{
    DCHECK(CalledOnValidThread());
    if (sync_client_)
        sync_client_->NotifyScreenStatus(status);
}
#endif

void SynchronousCompositorFrameSink::InvokeComposite(
    const gfx::Transform& transform,
    const gfx::Rect& viewport)
{
    did_submit_frame_ = false;
    // Adjust transform so that the layer compositor draws the |viewport| rect
    // at its origin. The offset of the |viewport| we pass to the layer compositor
    // is ignored for drawing, so its okay to not match the transform.
    // TODO(danakj): Why do we pass a viewport origin and then not really use it
    // (only for comparing to the viewport passed in
    // SetExternalTilePriorityConstraints), surely this could be more clear?
    gfx::Transform adjusted_transform = transform;
    adjusted_transform.matrix().postTranslate(-viewport.x(), -viewport.y(), 0);
    client_->OnDraw(adjusted_transform, viewport, in_software_draw_);

    if (did_submit_frame_) {
        // This must happen after unwinding the stack and leaving the compositor.
        // Usually it is a separate task but we just defer it until OnDraw completes
        // instead.
        client_->DidReceiveCompositorFrameAck();
    }
}

void SynchronousCompositorFrameSink::OnReclaimResources(
    uint32_t compositor_frame_sink_id,
    const cc::ReturnedResourceArray& resources)
{
    // Ignore message if it's a stale one coming from a different output surface
    // (e.g. after a lost context).
    if (compositor_frame_sink_id != compositor_frame_sink_id_)
        return;
    client_->ReclaimResources(resources);
}

void SynchronousCompositorFrameSink::SetMemoryPolicy(size_t bytes_limit)
{
    DCHECK(CalledOnValidThread());
    bool became_zero = memory_policy_.bytes_limit_when_visible && !bytes_limit;
    bool became_non_zero = !memory_policy_.bytes_limit_when_visible && bytes_limit;
    memory_policy_.bytes_limit_when_visible = bytes_limit;
    memory_policy_.num_resources_limit = kNumResourcesLimit;

    if (client_)
        client_->SetMemoryPolicy(memory_policy_);

    if (became_zero) {
        // This is small hack to drop context resources without destroying it
        // when this compositor is put into the background.
        context_provider()->ContextSupport()->SetAggressivelyFreeResources(
            true /* aggressively_free_resources */);
    } else if (became_non_zero) {
        context_provider()->ContextSupport()->SetAggressivelyFreeResources(
            false /* aggressively_free_resources */);
    }
}

void SynchronousCompositorFrameSink::DidActivatePendingTree()
{
    DCHECK(CalledOnValidThread());
    if (sync_client_)
        sync_client_->DidActivatePendingTree();
    DeliverMessages();
}

void SynchronousCompositorFrameSink::DeliverMessages()
{
    std::vector<std::unique_ptr<IPC::Message>> messages;
    std::unique_ptr<FrameSwapMessageQueue::SendMessageScope> send_message_scope = frame_swap_message_queue_->AcquireSendMessageScope();
    frame_swap_message_queue_->DrainMessages(&messages);
    for (auto& msg : messages) {
        Send(msg.release());
    }
}

bool SynchronousCompositorFrameSink::Send(IPC::Message* message)
{
    DCHECK(CalledOnValidThread());
    return sender_->Send(message);
}

bool SynchronousCompositorFrameSink::CalledOnValidThread() const
{
    return thread_checker_.CalledOnValidThread();
}

void SynchronousCompositorFrameSink::ReturnResources(
    const cc::ReturnedResourceArray& resources)
{
    DCHECK(resources.empty());
    client_->ReclaimResources(resources);
}

void SynchronousCompositorFrameSink::SetBeginFrameSource(
    cc::BeginFrameSource* begin_frame_source)
{
    // Software output is synchronous and doesn't use a BeginFrameSource.
    NOTREACHED();
}

} // namespace content
